<?php
/**
 * Nweb Framework
 *
 * This script is protected by copyright. It's use, copying, modification
 * and distribution without written consent of the author is prohibited.
 *
 * @category    source
 * @package     pl.nweb.framework.ftp
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt  GNU Lesser General Public
 * @version     3.0 $Id$
 * @link        http://code.google.com/p/nweb-framework/
 * @link        http://framework.nweb.pl
 */

namespace pl\nweb\framework\ftp;

/**
 * Katalog na serwerze FTP
 *
 * @category    source
 * @package     pl.nweb.framework.ftp
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @version     3.0 $Id$
 */
class Directory extends Element implements \RecursiveIterator
{
    /**
     * Lista elementów
     *
     * @var null|array
     */
    protected $_list = null;

    /**
     * Zawartość katalogu
     *
     * @var null|array
     */
    protected $_dirList = null;

    /**
     * Zawartość katalogu w formacie źródłowym serwera
     *
     * @var null|array
     */
    protected $_dirRawList = null;

    /**
     * Konstruktor
     *
     * @param Client $client klient ftp
     * @param string $path pełna ścieżka elementu
     */
    public function __construct(Client $client, $path)
    {
        $this->_server = $client;
        $this->_path   = rtrim($path, '/') . DIRECTORY_SEPARATOR;
    }

    /**
     * Zwraca zawartość katalogu
     *
     * @return array|null
     */
    public function getList()
    {
        if(null === $this->_dirList) {
            $this->_dirList = $this->getClient()->getList($this->_path);
        }
        return $this->_dirList;
    }

    /**
     * Zwraca zawartość katalogu w formacie źródłowym serwera
     *
     * @return array|null
     */
    public function getRawList()
    {
        if(null === $this->_dirRawList) {
            $this->_dirRawList = $this->getClient()->getRawList($this->_path);
        }
        return $this->_dirRawList;
    }

    /**
     * Przeszukuje zawartość pod kątem nazwy pliku lub katalogu i zwraca element
     *
     * @return null|Client_Element
     */
    public function find($name, $type = null)
    {
        $this->_prepare();
        foreach($this->_list as $elm) {
            if($elm->getName() === $name) {
                switch($type) {
                    case self::TYPE_DIRECTORY;
                        if ($elm->isDirectory()) {
                            return $elm;
                        }
                    break;
                    case self::TYPE_FILE;
                        if ($elm->isFile()) {
                            return $elm;
                        }
                    break;
                    default:
                        return $elm;
                }
            }
        }
        return null;
    }

    /**
     * Tworzy rekurencyjnie ścieżkę i zwraca obiekt folderu
     *
     * @return null|Client_Directory
     */
    public function createDir($path)
    {
        $path   = trim($path, DIRECTORY_SEPARATOR);
        $chunks = explode(DIRECTORY_SEPARATOR, $path);


        if(!empty($chunks[0])) {
            $find = $this->find($chunks[0], self::TYPE_DIRECTORY);
            if($find) {
                if(count($chunks)> 1) {
                    return $find->createDir(implode(DIRECTORY_SEPARATOR, array_slice($chunks, 1)));
                }
            } else {
                if($this->getClient()->createDir($this->getPath() . $chunks[0])) {
                    $this->refresh();
                    $find = $this->find($chunks[0], self::TYPE_DIRECTORY);
                    if(count($chunks)> 1) {
                        return $find->createDir(implode(DIRECTORY_SEPARATOR, array_slice($chunks, 1)));
                    }
                }
            }
            return $find;
        }
        return $this;
    }

    /**
     * Zmienia nazwę katalogu
     *
     * @param string $newname nowa nazwa katalogu
     * @return boolean
     * @throws Exception
     */
    public function rename($newname)
    {
        if($this->_path === '/') {
            return false;
        }

        $newname = basename($newname);
        $dir     = dirname($this->_path);

        return $this->getClient()->rename(
            rtrim($this->_path, DIRECTORY_SEPARATOR),
            $dir . DIRECTORY_SEPARATOR . $newname
         );
    }

    /**
     * Wysyła plik do wskazanego miejsca
     *
     * @param string $file plik źródłowy
     * @param integer $mode tryb FTP_ASCII|FTP_BINARY
     * @return boolean
     * @throws Exception
     */
    public function send($file, $mode = null)
    {
        return $this->getClient()->send(
            $file,
            $this->_path . basename($file),
            $mode
        );
    }

    /**
     * Odświeża listę elementów
     *
     * @return void
     */
    public function refresh()
    {
        $this->_list       = null;
        $this->_dirList    = null;
        $this->_dirRawList = null;
    }

    /**
     * Usuwa katalog wraz z zawartością
     *
     * @return void
     * @throws Exception
     */
    public function delete()
    {
        foreach($this as $elem) {
            $elem->delete();
        }
        $this->getClient()->deleteDir($this->_path);
    }

    /**
     * Zwraca flagę czy bierzący element jest katalogiem
     *
     * @return boolean
     */
    public function hasChildren()
    {
        return $this->current() instanceof self;
    }

    /**
     * Zwraca element
     *
     * @return mixed
     */
    public function getChildren()
    {
        return $this->current();
    }

    /**
     * Przesunięcie wskaźnika kolekcji na początek
     *
     * @return void
     */
    public function rewind()
    {
        $this->_prepare();
        reset($this->_list);
    }

    /**
     * Zwraca bieżacy element kolekcji
     *
     * @return mixed|false
     */
    public function current()
    {
        $this->_prepare();
        return current($this->_list);
    }

    /**
     * Zwraca bieżacy klucz elementu kolekcji
     *
     * @return string|integer
     */
    public function key ()
    {
        $this->_prepare();
        return key($this->_list);
    }

    /**
     * Przsuwa bieżący element do następnej pozycji
     *
     * @return false|mixed
     */
    public function next()
    {
        $this->_prepare();
        return next($this->_list);
    }

    /**
     * Zwraca flagę czy element jest prawidłowy
     *
     * @return boolean
     */
    public function valid()
    {
        $this->_prepare();
        return current($this->_list) !== false;
    }

    /**
     * Pobranie z katalogu listy elementów
     *
     * @throws Exception
     */
    protected function _prepare()
    {
        if (null === $this->_list) {
            $this->_list = array();

            $systemType = $this->getClient()->getSystemType();
            $list       = $this->getList();
            $rawList    = $this->getRawList();

            if ($list && $rawList) {
                switch ($systemType) {
                    case Client::SYSTEM_TYPE_UNIX:
                        foreach($list as $key => $name) {
                            if ($name == '.' || $name == '..') {
                                continue;
                            }
                            if (substr($rawList[$key], 0, 1) == 'd') {
                                $elm = new Directory($this->getClient(), $this->getPath() . $name);
                            } else {
                                $elm = new File($this, $this->getPath() . $name);
                            }
                            array_push($this->_list, $elm);
                        }
                    break;

                    case Client::SYSTEM_TYPE_WINDOWS_NT:
                        foreach($list as $key => $name) {
                            if (stripos($rawList[$key], '<DIR>') !== false) {
                                $elm = new Directory($this->getClient(), $this->getPath() . $name);
                            } else {
                                $elm = new File($this, $this->getPath() . $name);
                            }
                            array_push($this->_list, $elm);
                        }
                    break;

                    default:
                        throw new Exception(
                            Exception::MSG_UNSUPPORTED_SYSTEM,
                            Exception::COD_UNSUPPORTED_SYSTEM,
                            array($systemType)
                        );
                }
            }
        }
    }
}